#include "config.h"
#include <winsock2.h>
#include <windows.h>
#include <stdio.h>


#pragma comment(lib,"ws2_32.lib")


#include "aux_types.h"
#include "aux_errno.h"
#include "aux_socket.h"
#include "aux_debugmsg.h"
#include "aux_ip.h"

static u8 byWSLoaded = ERRNO_FALSE; /* default: not load the win socket dll */

static EErrNo Win32SocketPlatformInit()
{
#define __FUNCTION_NAME__ "Win32SocketPlatformInit"
    s32     nRtn = 0;
    WORD    wVersionRequested;
    WSADATA wsaData;

    wVersionRequested = MAKEWORD(2, 2);
    nRtn = WSAStartup(wVersionRequested, &wsaData);

    if (nRtn != 0)
    {
        byWSLoaded = ERRNO_FALSE;
        AUX_DEBUG_STR("WSAStartup fail\n");
        return -FERR(1);
    }

    if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
    {
        AUX_DEBUG_STR("wsa version not 2.2\n");
        WSACleanup();
        byWSLoaded = ERRNO_FALSE;
        return -FERR(2);
    }

    byWSLoaded = ERRNO_TRUE;
    return ERRNO_SUCCESS;
#undef __FUNCTION_NAME__

}

static EErrNo Win32SocketInit(TxIPCSocket *ptSocket)
{
#define __FUNCTION_NAME__ "Win32SocketInit"

    if (ERRNO_TRUE != byWSLoaded)
    {
        return -FERR(1);
    }

    if (ERRNO_SUCCESS != xIPCCommonChkSocketAction(ptSocket, SOCKET_ACTION_INIT))
    {
        AUX_DEBUG_STR("check action fail\n");
        return -ERRNO_EPARAMETER_INVALID;
    }
    xIPCPushCurSocketState(ptSocket, SOCKET_STATE_INITIALIZED);
    return ERRNO_SUCCESS;
#undef __FUNCTION_NAME__

}

static EErrNo Win32SocketCreate(TxIPCSocket *ptSocket,
                                            s32 nSendTimeout, s32 nRecvTimeout)
{
#define __FUNCTION_NAME__ "Win32SocketCreate"

    int recvTimeout = nRecvTimeout;
    int sendTimeout = nSendTimeout;


    if (ERRNO_TRUE != byWSLoaded)
    {
        return -FERR(1);
    }

    if (ERRNO_SUCCESS != xIPCCommonChkSocketAction(ptSocket, SOCKET_ACTION_CREAT))
    {
        AUX_DEBUG_STR("check action fail\n");
        return -ERRNO_EPARAMETER_INVALID;
    }
    ptSocket->nSocket = socket(AF_INET, SOCK_STREAM, 0);
    if (INVALID_SOCKET == ptSocket->nSocket)
    {
        xIPCPushCurSocketState(ptSocket, SOCKET_STATE_CREATE_FAIL);
        return -ERR(WSAGetLastError());
    }

    if (recvTimeout > 0)
        setsockopt(ptSocket->nSocket, SOL_SOCKET, SO_RCVTIMEO,
                    (char *)&recvTimeout ,sizeof(int));

    if (sendTimeout > 0)
        setsockopt(ptSocket->nSocket, SOL_SOCKET, SO_SNDTIMEO,
                    (char *)&sendTimeout ,sizeof(int));

    xIPCPushCurSocketState(ptSocket, SOCKET_STATE_CREATED);
    return ERRNO_SUCCESS;
#undef __FUNCTION_NAME__

}

static EErrNo Win32SocketConnect(TxIPCSocket *ptSocket)
{
#define __FUNCTION_NAME__ "Win32SocketConnect"

    SOCKADDR_IN tAddrSrv;

    if (ERRNO_TRUE != byWSLoaded)
    {
        return -FERR(1);
    }

    if (ERRNO_SUCCESS != xIPCCommonChkSocketAction(ptSocket,
                                                SOCKET_ACTION_CLIENT_CONNECT) ||
        ERRNO_FALSE == aux_chk_ipv4_addr_str(ptSocket->szIpStr))
    {
        AUX_DEBUG_STR("check action fail\n");
        return -ERRNO_EPARAMETER_INVALID;
    }
    if (ptSocket->wPort < 1024)
        aux_prt_warn("port %d < 1024, sth maybe wrong\n", ptSocket->wPort);

    memset((void *)&tAddrSrv, 0, sizeof(SOCKADDR_IN));
    tAddrSrv.sin_addr.S_un.S_addr = inet_addr(ptSocket->szIpStr);
    tAddrSrv.sin_family = AF_INET;
    tAddrSrv.sin_port = htons(ptSocket->wPort);
    if (connect(ptSocket->nSocket, (SOCKADDR*)&tAddrSrv, sizeof(SOCKADDR)))
    {
        /* connect fail */
        xIPCPushCurSocketState(ptSocket, SOCKET_STATE_CLIENT_CONNECT_FAIL);
        return -ERR(WSAGetLastError());
    }

    xIPCPushCurSocketState(ptSocket, SOCKET_STATE_CLIENT_CONNECT);
    return ERRNO_SUCCESS;
#undef __FUNCTION_NAME__

}

static EErrNo Win32SocketSend(TxIPCSocket *ptSocket,
                                          u8 *pabyData, s32 *pnSize)
{
#define __FUNCTION_NAME__ "Win32SocketSend"

    s32 nSizeGotoSend = 0;

    if (ERRNO_TRUE != byWSLoaded)
    {
        return -FERR(1);
    }

    if (ERRNO_SUCCESS !=
                    xIPCCommonChkSocketAction(ptSocket, SOCKET_ACTION_SEND) ||
        !pabyData || !pnSize)
    {
        AUX_DEBUG_STR("check action fail\n");
        return -ERRNO_EPARAMETER_INVALID;
    }

    if (0 == *pnSize)
    {
        goto EXIT;
    }

    nSizeGotoSend = *pnSize;

    *pnSize = send(ptSocket->nSocket, (const char *)pabyData, nSizeGotoSend, 0);

    /* if send fail */
    if (SOCKET_ERROR  == *pnSize)
    {
        s32 nErrorNo = WSAGetLastError();
        xIPCPushCurSocketState(ptSocket, SOCKET_STATE_SEND_FAIL);
        AUX_DEBUG_STR_INT("socket send error, err=", nErrorNo);
        AUX_DEBUG_RAW_STR("\n");
        *pnSize = 0;
        if (WSAECONNRESET == nErrorNo || WSAENOTSOCK == nErrorNo)
            return ERRNO_REMOTE_CLOSED;
        else
            return -ERR(nErrorNo);
    }

    /* maybe, not send all the data, but we place the size sended in *pnSize */

EXIT:
    xIPCPushCurSocketState(ptSocket, SOCKET_STATE_SEND);
    return ERRNO_SUCCESS;
#undef __FUNCTION_NAME__

}

static EErrNo Win32SocketRecv(TxIPCSocket *ptSocket,
                                          u8 *pabyData, s32 *pnSize)
{
#define __FUNCTION_NAME__ "Win32SocketRecv"

    s32 nSizeGotoRecv = 0;

    if (ERRNO_TRUE != byWSLoaded)
    {
        return -FERR(1);
    }

    if (ERRNO_SUCCESS != xIPCCommonChkSocketAction(ptSocket, SOCKET_ACTION_RECV))
    {
        AUX_DEBUG_STR("check action fail\n");
        AUX_DEBUG_STR_INT("current state", \
                    (xIPCGetSocketState(ptSocket, SOCKET_STATE_INDEX_CUR)));
        AUX_DEBUG_RAW_STR("\n");
        return -ERRNO_EPARAMETER_INVALID;
    }

    if (!pabyData || !pnSize)
    {
        AUX_DEBUG_STR("parameter error\n");
        return -ERRNO_EPARAMETER_INVALID;
    }

    if (0 == *pnSize)
        goto EXIT;

    nSizeGotoRecv = *pnSize;

    *pnSize = recv(ptSocket->nSocket, (char *)pabyData, nSizeGotoRecv, 0);
    // if socket has been closed, the return value is 0, or the byte cnt recved
    if (SOCKET_ERROR == *pnSize)
    {
        int nError = WSAGetLastError();
        if (WSAEWOULDBLOCK == nError || WSAETIMEDOUT == nError)
        {
            return ERRNO_TIMEOUT;
        }
        else if (WSAECONNRESET == nError || WSAENOTSOCK == nError)
        {
            AUX_DEBUG_STR("remote close socket\n");
            closesocket(ptSocket->nSocket);
            xIPCPushCurSocketState(ptSocket, SOCKET_STATE_CLOSE);
            return ERRNO_REMOTE_CLOSED;
        }
        else
        {
            xIPCPushCurSocketState(ptSocket, SOCKET_STATE_RECV_FAIL);
            AUX_DEBUG_STR_INT("recv err =", nError);
            AUX_DEBUG_RAW_STR("\n");
            return -ERR(nError);
        }
    }

    if (0 == *pnSize) /* the remote has closed */
    {
        closesocket(ptSocket->nSocket);
        xIPCPushCurSocketState(ptSocket, SOCKET_STATE_CLOSE);
        return ERRNO_REMOTE_CLOSED;
    }
EXIT:
    xIPCPushCurSocketState(ptSocket, SOCKET_STATE_RECV);
    return ERRNO_SUCCESS;
#undef __FUNCTION_NAME__

}

static EErrNo Win32SocketClose(TxIPCSocket *ptSocket)
{
#define __FUNCTION_NAME__ "Win32SocketClose"

    if (ERRNO_TRUE != byWSLoaded)
    {
        return -FERR(1);
    }

    if (ERRNO_SUCCESS != xIPCCommonChkSocketAction(ptSocket, SOCKET_ACTION_CLOSE))
    {
        AUX_DEBUG_STR("check action fail\n");
        return -ERRNO_EPARAMETER_INVALID;
    }

    if (closesocket(ptSocket->nSocket))
    {
        xIPCPushCurSocketState(ptSocket, SOCKET_STATE_CLOSE_FAIL);
        return -ERR(WSAGetLastError());
    }

    xIPCPushCurSocketState(ptSocket, SOCKET_STATE_CLOSE);
    return ERRNO_SUCCESS;
#undef __FUNCTION_NAME__

}

static EErrNo Win32SocketDeinit(TxIPCSocket *ptSocket)
{
#define __FUNCTION_NAME__ "Win32SocketDeinit"

    if (ERRNO_SUCCESS != xIPCCommonChkSocketAction(ptSocket,
                                                   SOCKET_ACTION_DEINIT))
    {
        AUX_DEBUG_STR("check action fail\n");
        return -ERRNO_EPARAMETER_INVALID;
    }
    xIPCPushCurSocketState(ptSocket, SOCKET_STATE_DEINITIALIZED);
    return ERRNO_SUCCESS;
#undef __FUNCTION_NAME__

}

static EErrNo Win32SocketPlatformDeinit()
{
#define __FUNCTION_NAME__ "Win32SocketPlatformDeinit"

    if (ERRNO_TRUE != byWSLoaded)
    {
        return ERRNO_SUCCESS;
    }

    if (WSACleanup())
    {
        byWSLoaded = ERRNO_FALSE;
        return -ERR(WSAGetLastError());
    }

    return ERRNO_SUCCESS;
#undef __FUNCTION_NAME__

}

static EErrNo Win32SocketShutdown(TxIPCSocket *ptSocket, u8 byRx, u8 byTx)
{
#define __FUNCTION_NAME__ "Win32SocketShutdown"
    int nHow;

    if (ERRNO_TRUE != byWSLoaded)
    {
        return -FERR(1);
    }

    if (ERRNO_SUCCESS != xIPCCommonChkSocketAction(ptSocket, SOCKET_ACTION_CLOSE))
    {
        AUX_DEBUG_STR("check action fail\n");
        return -ERRNO_EPARAMETER_INVALID;
    }

    if (!byRx && !byTx)
        return ERRNO_SUCCESS;

    if (byRx)
    {
        if (byTx)
            nHow = SD_BOTH;
        else
            nHow = SD_RECEIVE;
    }
    else
    {
        if (byTx)
            nHow = SD_SEND;
    }
    if (shutdown(ptSocket->nSocket, nHow))
    {
        xIPCPushCurSocketState(ptSocket, SOCKET_STATE_CLOSE_FAIL);
        return -ERR(WSAGetLastError());
    }

    xIPCPushCurSocketState(ptSocket, SOCKET_STATE_CLOSE);
    return ERRNO_SUCCESS;
#undef __FUNCTION_NAME__
}


/////////////////////////////////////////////////////////////////
////////////////////Server Specific//////////////////////////////
static EErrNo Win32SocketBind(TxIPCSocket *ptSocket)
{
#define __FUNCTION_NAME__ "Win32SocketBind"

    SOCKADDR_IN tAddrSrv;

    if (ERRNO_TRUE != byWSLoaded)
    {
        return -FERR(1);
    }

    if (ERRNO_SUCCESS != xIPCCommonChkSocketAction(ptSocket,
                                                   SOCKET_ACTION_SERVER_BIND) ||
        ERRNO_FALSE == aux_chk_ipv4_addr_str(ptSocket->szIpStr))
    {
        AUX_DEBUG_STR("check action fail\n");
        return -ERRNO_EPARAMETER_INVALID;
    }

    if (ptSocket->wPort < 1024)
        aux_prt_warn("port %d < 1024, sth maybe wrong\n", ptSocket->wPort);

    memset((void *)&tAddrSrv, 0, sizeof(SOCKADDR_IN));
    tAddrSrv.sin_addr.S_un.S_addr = inet_addr(ptSocket->szIpStr);
    tAddrSrv.sin_family           = AF_INET;
    tAddrSrv.sin_port             = htons(ptSocket->wPort);
    if (bind(ptSocket->nSocket, (SOCKADDR*)&tAddrSrv, sizeof(SOCKADDR)))
    {
        /* bind fail */
        xIPCPushCurSocketState(ptSocket, SOCKET_STATE_SERVER_BIND_FAIL);
        return -ERR(WSAGetLastError());
    }

    xIPCPushCurSocketState(ptSocket, SOCKET_STATE_SERVER_BIND);
    return ERRNO_SUCCESS;
#undef __FUNCTION_NAME__

}

static EErrNo Win32SocketListen(TxIPCSocket *ptSocket)
{
#define __FUNCTION_NAME__ "Win32SocketListen"
    if (ERRNO_TRUE != byWSLoaded)
    {
        return -FERR(1);
    }

    if (ERRNO_SUCCESS != xIPCCommonChkSocketAction(ptSocket,
                                                   SOCKET_ACTION_SERVER_LISTEN))
    {
        AUX_DEBUG_STR("check action fail\n");
        return -ERRNO_EPARAMETER_INVALID;
    }

    if (listen(ptSocket->nSocket, SOMAXCONN))
    {
        AUX_DEBUG_STR("listen fail\n");
        xIPCPushCurSocketState(ptSocket, SOCKET_STATE_SERVER_LISTEN_FAIL);
        return -ERR(WSAGetLastError());
    }

    xIPCPushCurSocketState(ptSocket, SOCKET_STATE_SERVER_LISTEN);
    return ERRNO_SUCCESS;
#undef __FUNCTION_NAME__
}

static EErrNo Win32SocketReset(TxIPCSocket *ptSocket)
{
#define __FUNCTION_NAME__ "Win32SocketReset"

    if (ERRNO_TRUE != byWSLoaded)
    {
        if (ptSocket)
            memset(ptSocket, 0, sizeof(TxIPCSocket));
        return ERRNO_SUCCESS;
    }

    Win32SocketClose(ptSocket);
    Win32SocketDeinit(ptSocket);
    return ERRNO_SUCCESS;
#undef __FUNCTION_NAME__

}

static EErrNo Win32SocketAccept(TxIPCSocket *ptLocal, TxIPCSocket *ptRemote)
{
#define __FUNCTION_NAME__ "Win32SocketAccept"
    //struct sockaddr_in addrRemote;
    SOCKADDR_IN addrRemote;
    int nLen = sizeof(addrRemote);

    if (ERRNO_TRUE != byWSLoaded)
    {
        return -FERR(1);
    }

    if (!ptLocal || !ptRemote ||
        ERRNO_SUCCESS != xIPCCommonChkSocketAction(ptLocal,
                                                   SOCKET_ACTION_SERVER_ACCEPT))
    {
        AUX_DEBUG_STR("check action fail\n");
        AUX_DEBUG_STR_INT("current state", \
                    (xIPCGetSocketState(ptLocal, SOCKET_STATE_INDEX_CUR)));
        AUX_DEBUG_RAW_STR("\n");
        return -ERRNO_EPARAMETER_INVALID;
    }
    memset(ptRemote, 0, sizeof(TxIPCSocket));
    ptRemote->nSocket = accept(ptLocal->nSocket,
                               (struct sockaddr *)&addrRemote,
                               &nLen);
    if (INVALID_SOCKET == ptRemote->nSocket)
    {
        /* accept has no timeout machnism */
        AUX_DEBUG_STR("accept fail\n");
        xIPCPushCurSocketState(ptLocal, SOCKET_STATE_SERVER_ACCEPT_FAIL);
        return -ERR(WSAGetLastError());
    }

    if (ERRNO_FAIL == aux_ipv4_num2str(addrRemote.sin_addr.s_addr,
                                          &(ptRemote->szIpStr[0]),
                                          sizeof((ptRemote->szIpStr))))
    {
        AUX_DEBUG_STR("aux_ipv4_num2str fail\n");
        memset(&(ptRemote->szIpStr[0]), 0, sizeof((ptRemote->szIpStr)));
    }

    xIPCPushCurSocketState(ptLocal, SOCKET_STATE_SERVER_ACCEPT);
    xIPCPushCurSocketState(ptRemote, SOCKET_STATE_RECV);
    ptRemote->eType = SOCKET_TYPE_REMOTE_CLIENT;

    return ERRNO_SUCCESS;
#undef __FUNCTION_NAME__
}

EErrNo aux_get_win32_socket_intf(TxIPCSocketInterface *pInterface)
{
    if (!pInterface)
        return -ERRNO_EPARAMETER_INVALID;

    memset(pInterface, 0, sizeof(TxIPCSocketInterface));

    pInterface->Init           = Win32SocketInit;
    pInterface->Create         = Win32SocketCreate;
    pInterface->Connect        = Win32SocketConnect;
    pInterface->Send           = Win32SocketSend;
    pInterface->Recv           = Win32SocketRecv;
    pInterface->Close          = Win32SocketClose;
    pInterface->Deinit         = Win32SocketDeinit;
    pInterface->Bind           = Win32SocketBind;
    pInterface->Listen         = Win32SocketListen;
    pInterface->Accept         = Win32SocketAccept;
    pInterface->Shutdown       = Win32SocketShutdown;
    pInterface->Reset          = Win32SocketReset;
    pInterface->PlatformDeinit = Win32SocketPlatformDeinit;
    pInterface->PlatformInit   = Win32SocketPlatformInit;

    return ERRNO_SUCCESS;
}

